package com.sunchangpeng.wechat.core.utils;

import com.sunchangpeng.wechat.core.exception.WechatClientException;
import org.apache.commons.lang3.StringUtils;

import javax.xml.bind.*;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.google.common.base.Verify.verifyNotNull;

public final class XmlUtils {
    private static final ConcurrentMap<Class<?>, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64);

    public static <T> T tobean(String xml, Class<? extends T> clazz) {
        try {
            Unmarshaller unmarshaller = createUnmarshaller(clazz);
            return (T) unmarshaller.unmarshal(new StringReader(xml));
        } catch (UnmarshalException ex) {
            throw new WechatClientException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex);
        } catch (JAXBException ex) {
            throw new WechatClientException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
        }
    }

    public static String toxml(Object obj) {
        return toxml(obj, "UTF-8", false, false);
    }

    public static String toxml(Object obj, boolean format, boolean fragment) {
        return toxml(obj, "UTF-8", format, fragment);
    }

    public static String toxml(Object obj, String encoding, boolean format, boolean fragment) {
        try {
            Class<?> clazz = obj.getClass();
            Marshaller marshaller = createMarshaller(clazz);
            customizeMarshaller(marshaller, encoding, format, fragment);
            StringWriter result = new StringWriter();
            marshaller.marshal(obj, result);
            return result.toString();
        } catch (MarshalException ex) {
            throw new WechatClientException("Could not marshal [" + obj + "]: " + ex.getMessage(), ex);
        } catch (JAXBException ex) {
            throw new WechatClientException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
        }
    }

    private static void customizeMarshaller(Marshaller marshaller, String encoding, boolean format, boolean fragment) throws PropertyException {
        if (StringUtils.isNotBlank(encoding)) {
            marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);// 编码格式
        }

        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, format);// 是否格式化生成的xml串
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, fragment);// 是否省略xm头声明信息
    }

    private static Marshaller createMarshaller(Class<?> clazz) throws JAXBException {
        JAXBContext jaxbContext = getJaxbContext(clazz);
        Marshaller marshaller = jaxbContext.createMarshaller();
        return marshaller;
    }

    private static Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException {
        JAXBContext jaxbContext = getJaxbContext(clazz);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        return unmarshaller;
    }

    private static JAXBContext getJaxbContext(Class<?> clazz) throws JAXBException {
        verifyNotNull(clazz, "'clazz' must not be null");
        JAXBContext jaxbContext = jaxbContexts.get(clazz);
        if (jaxbContext == null) {
            jaxbContext = JAXBContext.newInstance(clazz);
            jaxbContexts.putIfAbsent(clazz, jaxbContext);
        }

        return jaxbContext;
    }
}
